/***************************************************************
 * Name:      GanitSheetsApp.cpp
 * Purpose:   Code for Application Class
 * Author:    Milind Gupta (milind.gupta@gmail.com)
 * Website:   https://code.google.com/p/ganitsheets/
 * Created:   2011-02-12
 * Copyright: Milind Gupta ()
 * License:
 **************************************************************/

#include "GanitSheetsMain.h"

// The LUA API definition for GanitSheetsFrame
luaObj::luaAPIStruct GanitSheetsFrame::luaAPITable[] = {
    {CREATEMENU,"createMenu","",true,false,0},
    {QUIT,"quit","",true,false,0},
    {ABOUT,"about","",true,false,0},
    {ADDLOGMSG,"print","",true,false,0},
    {ADDSHEET,"addSheet","",true,false,0},
    luaObj::emptyLuaAPI[0] // Null structure to indicate end of API
};

luaObj::luaAPIStruct GanitSheetsFrame::luaStaticAPITable[] = {
    {ADDLOGMSG,"print","",true,true,0},
    luaObj::emptyLuaAPI[0] // Null structure to indicate end of API
};

GanitSheetsFrame::GanitSheetsFrame(wxWindow* parent, GanitSheetsApp *parentApp, \
        wxWindowID id, unsigned long logLength) : luaObj(luaAPITable, luaStaticAPITable)
{
    this->parentApp = parentApp;
    // Setup the IDs for the basic elements
    this->frameIDs = new idList;
    this->spSheets = new spreadSheetList;

    this->Create(parent, id, _("GanitSheets"), wxDefaultPosition, wxDefaultSize, \
           wxDEFAULT_FRAME_STYLE, _T("Main GanitSheets Frame"));
    this->SetClientSize(wxSize(640,480));
    this->SetMinSize(wxSize(500,400));

    // Add the Split Window to divide the frame to place the 2 notebooks
    this->splitWindowGS = new wxSplitterWindow(this, \
            this->frameIDs->AddID("splitWindowGS")->idVal, wxPoint(56,64), \
            wxSize(120,64), wxSP_3D, _T("splitWindowGS"));
    this->splitWindowGS->SetMinSize(wxSize(10,10));
    this->splitWindowGS->SetMinimumPaneSize(10);
    // Create the 2 Notebooks
    this->sheetsBook = new wxNotebook(this->splitWindowGS, \
            this->frameIDs->AddID("sheetsBook")->idVal, wxPoint(102,90), \
            wxDefaultSize, wxNB_BOTTOM, _T("sheetsBook"));
    this->commandBook = new wxNotebook(this->splitWindowGS, \
            this->frameIDs->AddID("commandBook")->idVal, wxDefaultPosition, \
            wxDefaultSize, wxNB_TOP, _T("commandBook"));

    // Add pages to the Command Book for Data Entry and Log and Commands

    // The Data Page and text box
    this->PanelData = new wxPanel(commandBook, \
            this->frameIDs->AddID("PanelData")->idVal, wxDefaultPosition, \
            wxDefaultSize, wxTAB_TRAVERSAL, _T("PanelData"));
    this->BoxSizerData = new wxBoxSizer(wxHORIZONTAL);
    this->TextData = new wxTextCtrl(this->PanelData, \
            this->frameIDs->AddID("TextData")->idVal, wxEmptyString, \
            wxDefaultPosition, wxDefaultSize, wxTE_AUTO_SCROLL|wxTE_MULTILINE, \
            wxDefaultValidator, _T("TextData"));
    this->BoxSizerData->Add(this->TextData, 1, \
            wxALL|wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 1);
    this->PanelData->SetSizer(this->BoxSizerData);
    this->BoxSizerData->Fit(this->PanelData);
    this->BoxSizerData->SetSizeHints(this->PanelData);

    // The Log and Commands Page
    this->PanelLogComm = new wxPanel(this->commandBook, \
            this->frameIDs->AddID("PanelLogComm")->idVal, wxDefaultPosition, \
            wxDefaultSize, wxTAB_TRAVERSAL, _T("PanelLogComm"));
    this->BoxSizerLogComm = new wxBoxSizer(wxHORIZONTAL);
    this->splitLogCommands = new wxSplitterWindow(this->PanelLogComm, \
            this->frameIDs->AddID("splitLogCommands")->idVal, wxDefaultPosition, \
            wxDefaultSize, wxSP_3D, _T("splitLogCommands"));
    this->splitLogCommands->SetMinSize(wxSize(10,10));
    this->splitLogCommands->SetMinimumPaneSize(10);
    this->TextLog = new wxTextCtrl(this->splitLogCommands, \
            this->frameIDs->AddID("TextLog")->idVal, wxEmptyString, \
            wxPoint(107,108), wxSize(209,178), wxTE_AUTO_SCROLL|wxTE_READONLY|wxTE_MULTILINE, \
            wxDefaultValidator, _T("TextLog"));
    // Add the Panel and the text box for the commands
    this->PanelComm = new wxPanel(this->splitLogCommands, \
            this->frameIDs->AddID("PanelComm")->idVal, wxPoint(385,83), \
            wxDefaultSize, wxTAB_TRAVERSAL, _T("PanelComm"));
    this->BoxSizerComm = new wxBoxSizer(wxVERTICAL);
    this->TextComm = new wxTextCtrl(this->PanelComm, \
            this->frameIDs->AddID("TextComm")->idVal, wxEmptyString, \
            wxDefaultPosition, wxDefaultSize, wxTE_AUTO_SCROLL|wxTE_MULTILINE, \
            wxDefaultValidator, _T("TextComm"));
    this->BoxSizerComm->Add(this->TextComm, 1, \
            wxALL|wxEXPAND|wxALIGN_TOP|wxALIGN_CENTER_HORIZONTAL, 3);
    // Add the 2 buttons to execute the code or clear the command box
    this->BoxSizerCommButtons = new wxBoxSizer(wxHORIZONTAL);
    this->ButtonExec = new wxButton(this->PanelComm, \
            this->frameIDs->AddID("ButtonExec")->idVal, _(">"), \
            wxDefaultPosition, wxSize(46,23), 0, wxDefaultValidator, \
            _T("ButtonExec"));
    this->ButtonExec->SetMinSize(wxSize(25,25));
    this->BoxSizerCommButtons->Add(this->ButtonExec, 0, \
            wxALL|wxFIXED_MINSIZE|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
    this->ButtonClear = new wxButton(this->PanelComm, \
            this->frameIDs->AddID("ButtonClear")->idVal, _("..."), \
            wxDefaultPosition, wxSize(39,23), 0, wxDefaultValidator, \
            _T("ButtonClear"));
    this->ButtonClear->SetMinSize(wxSize(25,25));
    this->BoxSizerCommButtons->Add(this->ButtonClear, 0, \
            wxALL|wxFIXED_MINSIZE|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
    this->BoxSizerComm->Add(this->BoxSizerCommButtons, 0, \
            wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 0);
    this->PanelComm->SetSizer(this->BoxSizerComm);
    this->BoxSizerComm->Fit(this->PanelComm);
    this->BoxSizerComm->SetSizeHints(this->PanelComm);
    this->splitLogCommands->SplitVertically(this->TextLog, this->PanelComm);
    this->BoxSizerLogComm->Add(this->splitLogCommands, 1, \
            wxALL|wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 3);
    this->PanelLogComm->SetSizer(this->BoxSizerLogComm);
    this->BoxSizerLogComm->Fit(this->PanelLogComm);
    this->BoxSizerLogComm->SetSizeHints(this->PanelLogComm);

    // Finally add the pages to the commandBook notebook
    this->commandBook->AddPage(this->PanelLogComm, _("Log, Commands"), false);
    this->commandBook->AddPage(this->PanelData, _("Data"), false);
    this->splitWindowGS->SplitHorizontally(this->sheetsBook, this->commandBook);

    // Number of lines in the log
    this->logLines = logLength;

    long id1 = wxNewId();
    long id2 = wxNewId();
    // Create the Menu Bar object
    menuMainGS = new wxMenuBar;
    // Create the file menu object
    wxMenu *menuFile = new wxMenu;

    // Add the About and Quit commands to the file menu
    menuFile->Append( id2, _("&About...") );
    menuFile->AppendSeparator();
    menuFile->Append( id1, _("Q&uit") );
    // Add the file menu to the menu bar
    menuMainGS->Append(menuFile, _("&File") );

    // Add the menu bar to the frame
    SetMenuBar(menuMainGS);

    CreateStatusBar();

    SetStatusText( _("Welcome to GanitSheets!") );

    // Connect the events here
    Connect(this->frameIDs->getIDbyName("ButtonClear")->idVal, \
            wxEVT_COMMAND_BUTTON_CLICKED, \
            (wxObjectEventFunction)&GanitSheetsFrame::OnButtonClearClick);
    Connect(this->frameIDs->getIDbyName("ButtonExec")->idVal, \
            wxEVT_COMMAND_BUTTON_CLICKED, \
            (wxObjectEventFunction)&GanitSheetsFrame::OnButtonExecClick);
    Connect( id1, wxEVT_COMMAND_MENU_SELECTED, \
            (wxObjectEventFunction) &GanitSheetsFrame::OnQuit );
    Connect( id2, wxEVT_COMMAND_MENU_SELECTED, \
                    (wxObjectEventFunction) &GanitSheetsFrame::OnAbout );

    // Register the lua API here
    this->registerLuaStaticAPI();       // Register the Static API and use the hier prefix
    this->registerAPILudonGlobal("APPGS");    // Register the Static API and do not use the hier prefix
}

// To add a spreadsheet
spreadSheet* GanitSheetsFrame::addSheet(shtSize *SheetSize= (&defaultSheetSize))
{
    spreadSheet *newSheet;
    unsigned short sheetNum;
    std::string shtName;
    listIterator<spreadSheet> *spIter;
    wxBoxSizer *sheetBoxSizer;
    wxGrid *newGrid;
    wxPanel *newPanel;
    int gridID, panelID;

    sheetNum = 1;
    shtName = "Sheet 1";
    // Get an iterator for the list of spreadsheets here
    spIter = this->spSheets->getIterator();
    // Iterate through the list to create a name for this new sheet
    newSheet = spIter->nextStructListItem();
    while(newSheet)
    {
        if(newSheet->sheetName() == shtName)
        {
            // Choose the next name
            sheetNum++;
            shtName = std::string("Sheet ") + toString(sheetNum);
            // Reset the Iterator to search the list again
            spIter->pointToStart();
        }
        newSheet = spIter->nextStructListItem();
    }
    // Remove the iterator
    this->spSheets->removeIterator(spIter);
    // Add the grid control to the list of spreadsheet
    // Now add the spreadsheet to the GUI
    gridID = wxNewId();
    panelID = wxNewId();
    newPanel = new wxPanel(this->sheetsBook, panelID, wxDefaultPosition, wxDefaultSize, \
        wxTAB_TRAVERSAL, wxString((std::string("spSheetPanel:") + shtName).c_str(),wxConvUTF8));
    sheetBoxSizer = new wxBoxSizer(wxHORIZONTAL);
    newGrid = new wxGrid(newPanel, gridID, wxDefaultPosition, wxDefaultSize, 0, \
        wxString((std::string("spSheet:") + shtName).c_str(),wxConvUTF8));

    newSheet = this->spSheets->AddSheet(newGrid, newPanel, panelID, gridID, SheetSize, shtName);
    this->frameIDs->AddID(std::string("spSheetPanel:") + shtName, panelID);
    this->frameIDs->AddID(std::string("spSheet:") + shtName, gridID);

    newGrid->CreateGrid(SheetSize->rows,SheetSize->columns);
    newGrid->EnableEditing(true);
    newGrid->EnableGridLines(true);
    newGrid->SetDefaultCellFont( newGrid->GetFont() );
    newGrid->SetDefaultCellTextColour( newGrid->GetForegroundColour() );
    sheetBoxSizer->Add(newGrid, 1, \
        wxALL|wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 1);
    newPanel->SetSizer(sheetBoxSizer);
    sheetBoxSizer->Fit(newPanel);
    sheetBoxSizer->SetSizeHints(newPanel);
    this->sheetsBook->AddPage(newPanel, \
        wxString(shtName.c_str(),wxConvUTF8), false);

    spreadSheet::cell *newCell;
    newCell = newSheet->getCell(4,4);
    newCell->setValue("Hello!");
    return newSheet;
}

// Frame destructor
GanitSheetsFrame::~GanitSheetsFrame()
{
}

void GanitSheetsFrame::OnButtonClearClick(wxCommandEvent& event)
{
    this->TextComm->Clear();
}

void GanitSheetsFrame::OnButtonExecClick(wxCommandEvent& event)
{
    // Execute the code in TextComm as a Lua Chunk
    this->addLogMsg("Executing Chunk...");
    int error;
    error = luaL_loadbuffer(luaVM, this->TextComm->GetValue().mb_str(), \
            strlen(this->TextComm->GetValue().mb_str()), "Chunk") || \
            lua_pcall(luaVM, 0, 0, 0);
    if (error)
    {
        this->addLogMsg(std::string(lua_tostring(luaVM, -1)));
        lua_pop(luaVM, 1);  /* pop error message from the stack */
    }
    this->addLogMsg("End Execution.");
}

void GanitSheetsFrame::addLogMsg(std::string Msg)
{
    this->TextLog->SetValue(this->TextLog->GetValue() + wxString((Msg+"\n").c_str(),wxConvUTF8));
}

void GanitSheetsFrame::createMenu()
{
    // The Stack top element should be a table
    // The table is an array with each element a sub table
    // The sub table has 2 key value pairs:
    // Name - containing the name of the item to be displayed on the menu
    // Value - containing a sub table for a sub-Menu
    if(!lua_istable(luaVM,-1))
    {
        lua_pushnumber(luaVM,0);
        return;   // Error needs the table with the menu structure data
    }
    // Traverse the table key value pairs here:
    int tableKey = lua_gettop(luaVM);
    int numOfItems = 0;
    wxMenu *createdMenu;
    lua_pushnil(luaVM); // The first key for lua_next iterator function
    while (lua_next(luaVM, tableKey) != 0)
    {
        // now 'key' is at index -2 and 'value' is at index -1
        // The value needs to be a table
        if(lua_istable(luaVM,-1))
        {
            //this->addLogMsg("createMenu: Main Table value is table");
            // Get the Value from this table
            lua_getfield(luaVM,-1,std::string("Value").c_str());
            if(!lua_isnil(luaVM,-1))
            {
                //this->addLogMsg("createMenu: Found Value key");
                lua_getfield(luaVM,-2,std::string("Name").c_str());
                if(!lua_isnil(luaVM,-1))
                {
                    //this->addLogMsg("createMenu: Found Name key");
                    if(lua_istable(luaVM,-2))   // the Value is a table and hence submenu
                    {
                        //this->addLogMsg("createMenu: Value is a table");
                        lua_pop(luaVM,1);   // Remove the Name from the top of the stack so that Value table is now on top
                        createdMenu = this->createSubMenu(numOfItems);
                        // Now get the name
                        lua_getfield(luaVM,-2,std::string("Name").c_str());
                        // Add the menu to the menu bar
                        menuMainGS->Append(createdMenu, wxString(lua_tostring(luaVM,-1),wxConvUTF8));
                        numOfItems++;
                    }   // if(lua_istable(luaVM,-2)) to check if the Value in the sub table is a sub table at this level it should be
                }   // if(!lua_isnil(luaVMM,-1)) to check if sub tables has the key Name
                lua_pop(luaVM,1);   // Remove the Name from the stack
            }   // if(!lua_isnil(luaVM,-1)) to check if sub tables has the key Value
            lua_pop(luaVM,1); // Pop off the Value from the stack
        }   // if(lua_istable(luaVM,-1)) for the main table values ends here
        // removes 'value'; keeps 'key' for next iteration
        lua_pop(luaVM, 1);
    }   // while (lua_next(luaVM, tableKey) != 0) ends
    lua_pushnumber(luaVM,numOfItems);
}
//the Lua code to be associated with the item or
wxMenu *GanitSheetsFrame::createSubMenu(int &numOfItems)
{
    // This function expects a table whose sub menu has to be created on top of the lua stack
    if(lua_gettop(luaVM)==0)
        return 0;       // Expecting the table on top
    if(!lua_istable(luaVM,-1))
        return 0;       // Expecting the table on top
    // Traverse the table key value pairs here:
    int tableKey = lua_gettop(luaVM);
    int id;
    wxMenu *createdMenu, *createdSubMenu;
    createdMenu = new wxMenu;
    lua_pushnil(luaVM); // The first key for lua_next iterator function
    while (lua_next(luaVM, tableKey) != 0)
    {
        // now 'key' is at index -2 and 'value' is at index -1
        // Value needs to be a table
        if(lua_istable(luaVM,-1))
        {
            // Table is at index -1
            // Get the Value from this table
            //this->addLogMsg("createSubMenu: Main Table value is table");
            lua_getfield(luaVM,-1,std::string("Value").c_str());
            if(!lua_isnil(luaVM,-1))
            {
                //this->addLogMsg("createSubMenu: Found Value key");
                lua_getfield(luaVM,-2,std::string("Name").c_str());
                if(!lua_isnil(luaVM,-1))
                {
                    //this->addLogMsg("createSubMenu: Found Name key");
                    if(lua_istable(luaVM,-2))   // the Value is a table and hence submenu
                    {
                        //this->addLogMsg("createSubMenu: Value is a table");
                        lua_pop(luaVM,1);   // Remove the Name from the top of the stack so that Value table is now on top
                        createdSubMenu = this->createSubMenu(numOfItems);   // Recurse to create Sub Menu
                        // Now get the name again
                        lua_getfield(luaVM,-2,std::string("Name").c_str());
                        // Add the menu to the created Menu
                        createdMenu->Append(this->frameIDs->AddID(std::string(lua_tostring(luaVM,-1)))->idVal, \
                            wxString(lua_tostring(luaVM,-1),wxConvUTF8), createdSubMenu);
                        numOfItems++;
                    }   // if(lua_istable(luaVM,-2)) to check if the Value in the sub table is a sub table at this level it should be
                    else
                    {
                        //this->addLogMsg("createMenu: Value is a normal Menu Item");
                        // Value is not a table so a simple Menu Item add it to the createdMenu
                        id = this->frameIDs->AddID(std::string(lua_tostring(luaVM,-2)))->idVal;
                        createdMenu->Append(id,wxString(lua_tostring(luaVM,-1),wxConvUTF8));
                        // Connect the event
                        Connect( id, wxEVT_COMMAND_MENU_SELECTED, \
                            (wxObjectEventFunction) &GanitSheetsFrame::menuEventHandler);
                        numOfItems++;
                    }
                }   // if(!lua_isnil(luaVMM,-1)) to check if sub tables has the key Name
                lua_pop(luaVM,1);   // Remove the Name from the stack
            }   // if(!lua_isnil(luaVM,-1)) to check if sub tables has the key Value
            lua_pop(luaVM,1); // Pop off the Value from the stack
        }   //if(lua_istable(luaVM,-1)) to check if the main Value is a table with "Name" and "Value" keys
        // removes 'value'; keeps 'key' for next iteration
        lua_pop(luaVM, 1);
    }   // while (lua_next(luaVM, tableKey) != 0) Iterating through the table
    return createdMenu;
}

void GanitSheetsFrame::menuEventHandler(wxCommandEvent& event)
{
    // Execute the Lua code associated with the id
    int error, id;
    id = event.GetId();
    error = luaL_loadbuffer(luaVM, this->frameIDs->getIDbyVal(id)->idName.c_str(), \
            strlen(this->frameIDs->getIDbyVal(id)->idName.c_str()), "Chunk") || \
            lua_pcall(luaVM, 0, 0, 0);
    if (error)
    {
        this->addLogMsg(std::string(lua_tostring(luaVM, -1)));
        lua_pop(luaVM, 1);  /* pop error message from the stack */
    }
}

// Exit command for the frame
void GanitSheetsFrame::OnQuit(wxCommandEvent& event)
{
    this->quit();
}

void GanitSheetsFrame::quit()
{
    Close();
}

// About command selected from meny
void GanitSheetsFrame::OnAbout(wxCommandEvent& event)
{
    this->about();
}

void GanitSheetsFrame::about()
{
    wxMessageBox(wxString((std::string("GanitSheets Version ")+std::string(VERSION)).c_str(),wxConvUTF8), \
                 _("Welcome to..."));
}

int GanitSheetsFrame::LuaScriptCall(int funcIndex)
{
    switch(funcIndex)
    {
        case DEBUG:
        {
            this->addLogMsg("I am here in DEBUG.");
            return 0;
            break;
        }
        case ADDLOGMSG:
        {
            // Replicating the Lua print function here to redirect it to the log window
            int n = lua_gettop(luaVM);  // number of arguments
            int i;
            std::string logMsg,s;
            lua_getglobal(luaVM, "tostring");
            for (i=1; i<=n; i++)
            {
                const char *s;
                lua_pushvalue(luaVM, -1);  // function to be called
                lua_pushvalue(luaVM, i);   // value to print
                lua_call(luaVM, 1, 1);
                s = lua_tostring(luaVM, -1);  // get result
                if (s == NULL)
                  return luaL_error(luaVM, LUA_QL("tostring") " must return a string to "
                                       LUA_QL("print"));
                if (i>1) logMsg = logMsg + std::string("\t");
                logMsg = logMsg + std::string(s);
                lua_pop(luaVM, 1);  // pop result
            }
            this->addLogMsg(logMsg);
            return 0;
            break;
        }
        case CREATEMENU:
        {
            this->createMenu();
            return 1;
            break;
        }
        case QUIT:
        {
            this->quit();
            return 0;
            break;
        }
        case ABOUT:
        {
            this->about();
            return 0;
            break;
        }
        case ADDSHEET:
        {
            this->addSheet();
            return 0;
            break;
        }
    }
    return 0;
}
